import json
from inspect import currentframe, getframeinfo
import re
import os

def validate_layout ( jlayout_str , ids, functions ) :

    jlayout_str = jlayout_str.lstrip()

    if jlayout_str[0] == '{' :
        jlayout_str = '[ ' + jlayout_str + ' ]' 

    jlayout = json.loads( jlayout_str )

    for obj in jlayout : 

        # check validity

        if obj["type"] == "" :
            print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
            print(str(ids))
            print(str(functions))
            return 1

        if obj["id"] == "" :
            print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
            print(str(ids))
            print(str(functions))
            return 1

        if " " in obj["id"] :
            print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
            print(str(ids))
            print(str(functions))
            return 1

        if ids != None :
            if obj["id"] in ids :
                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                print(str(ids))
                print(str(functions))
                return 1

        ids = ids + [ obj["id"] ]

        if obj["type"] == "rows" :

            if obj["display"] == "" :
                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                print(str(ids))
                print(str(functions))
                return 1

            if obj["id"] == "" :
                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                print(str(ids))
                print(str(functions))
                return 1

            if ( obj["expand"] != "on" ) and ( obj["expand"] != "off" ) :
                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                print(str(ids))
                print(str(functions))
                return 1

            for row in obj["rows"] : 

                if ( row["type"] != "rows" ) and ( row["type"] != "row" ) :
                    print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                    print(str(ids))
                    print(str(functions))
                    return 1

                if ( row["type"] == "rows" ) :
                    if ( validate_layout ( '[ ' + str(json.dumps(row)) + ' ]', ids, functions ) == 1 ):
                        print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                        print(str(ids))
                        print(str(functions))
                        return 1

                elif ( row["type"] == "row" ) :
                    if ( row["permission"] != "read_write" ) and ( row["permission"] != "read_only" ) :
                        print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                        print(str(ids))
                        print(str(functions))
                        return 1

                    if row["id"] == "" :
                        print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                        print(str(ids))
                        print(str(functions))
                        return 1

                    if ids != None :
                        if row["id"] in ids :
                            print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                            print(str(ids))
                            print(str(functions))
                            return 1

                    ids = ids + [ row["id"] ]

                    for field in row["fields"] :
                        if ( field["type"] != "textbox" ) and ( field["type"] != "string" ) and \
                           ( field["type"] != "button" ) and ( field["type"] != "void" ) and \
                           ( field["type"] != "path" ) and ( field["type"] != "combobox" ) and \
                           ( field["type"] != "archive" ) and ( field["type"] != "rows" ):
                            print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                            print(str(ids))
                            print(str(functions))
                            return 1

                        if ( field["type"] == "textbox" ) :
                            if ( field["display"] == "" ) :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                        if ( field["type"] == "archive" ) :

                            if "selection" not in field :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                            items_display_list = []
                            default_item = field["selection"]
                            default_item_found = 0 

                            if ( field["selection"] == "" ) :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                            for item in field["items"] :
                                if ( item["data"] == "" ) :
                                    print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                    print(str(ids))
                                    print(str(functions))
                                    return 1

                                if ( item["display"] == "" ) :
                                    print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                    print(str(ids))
                                    print(str(functions))
                                    return 1


                                if item["display"] in items_display_list :
                                    print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                    print(str(ids))
                                    print(str(functions))
                                    return 1

                                items_display_list = items_display_list + [item["display"]]
                                if default_item == item["display"] :
                                    default_item_found = 1

                            if default_item_found == 0 :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1


                        if ( field["type"] == "button" ) :

                            if ( field["display"] == "" ) :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                            if ( field["thread"] != "on" ) and ( field["thread"] != "off" ) :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                            if ( field["function"] == "" ) :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                            if functions != None :
                                if field["function"] in functions :
                                    print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                    print(str(ids))
                                    print(str(functions))
                                    return 1

                            functions = functions + [ field["function"] ]

                        if ( field["type"] == "path" ) :
                            if "display" not in field :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                            if (field["target"] != "directory") and (field["target"] != "file") :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                            if (field["direction"] != "input") and (field["direction"] != "output") :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1


                        if ( field["type"] == "string" ) :

                            if ( field["thread"] != "on" ) and ( field["thread"] != "off" ) :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                            if ( field["thread"] == "on" ) and ( field["button"] == "off" ) :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                            if ( field["visibility"] != "open" ) and ( field["visibility"] != "closed" ) :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                            if (( field["button"] != "on" ) and ( field["button"] != "off" )) :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                            if (( field["checkbox"] != "enable" ) and ( field["checkbox"] != "disable" ) and ( field["checkbox"] != "off" )) :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                            if (( field["checkbox"] == "enable" ) and ( field["button"] == "on" )) :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                            if (( field["checkbox"] == "disable" ) and ( field["button"] == "on" )) :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                            if functions != None :
                                if field["function"] in functions :
                                    print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                    print(str(ids))
                                    print(str(functions))
                                    return 1

                            if "button_display" not in field :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                            if ( field["button"] == "on" ) :
                                if ( field["button_display"] == "" ) :
                                    print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                    print(str(ids))
                                    print(str(functions))
                                    return 1

                        if ( field["type"] == "combobox" ) :

                            if (( field["checkbox"] != "enable" ) and ( field["checkbox"] != "disable" ) and ( field["checkbox"] != "off" )) :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                            if (( field["checkbox"] == "enable" ) and ( field["button"] == "on" )) :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                            if (( field["checkbox"] == "disable" ) and ( field["button"] == "on" )) :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1


                            if functions != None :
                                if field["function"] in functions :
                                    print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                    print(str(ids))
                                    print(str(functions))
                                    return 1

                            if field["function"] != "" :
                                functions = functions + [ field["function"] ]

                            if (( field["button"] != "on" ) and ( field["button"] != "off" )) :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                            if "selection" not in field :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1

                            items_display_list = []
                            default_item = field["selection"]
                            default_item_found = 0 

                            for item in field["items"] :
                                if ( item["display"] == "" ) :
                                    print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                    print(str(ids))
                                    print(str(functions))
                                    return 1

                                if item["display"] in items_display_list :
                                    print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                    print(str(ids))
                                    print(str(functions))
                                    return 1

                                items_display_list = items_display_list + [item["display"]]
                                if default_item == item["display"] :
                                    default_item_found = 1

                            if default_item_found == 0 :
                                print("validate_layout " + str(getframeinfo(currentframe()).lineno) )
                                print(str(ids))
                                print(str(functions))
                                return 1
    return 0


def get_from_layout ( jlayout_str, element ) :

    jlayout_str = jlayout_str.lstrip()

    if jlayout_str[0] == '{' :
        jlayout_str = '[ ' + jlayout_str + ' ]' 

    jlayout = json.loads( jlayout_str )

    for obj in jlayout : 
        if obj["id"] == element :
            return obj

        if obj["type"] == "rows" :
            for row in obj["rows"] :
                if row["id"] == element :
                    return row
                if row["type"] == "rows" :
                    ret = get_from_layout ( '[ ' + str(json.dumps(row)) + ' ]', element )
                    if ret != "" :
                        return ret

    return ""
             

def set_to_layout ( jlayout_str, jobj ) :
    if ( 0 == validate_layout ( jlayout_str , [] , [] )) :
        if ( 0 == validate_layout ( json.dumps(jobj) , [] , [] )) :

            # format input object into string
            sobj = json.dumps(jobj)

            # reformat input string
            jlayout = json.loads(jlayout_str)
            jlayout_str = json.dumps(jlayout)

            # get layout object to substitute and format it
            jobj_layout = get_from_layout(jlayout_str, jobj["id"])
            jobj_layout_str = json.dumps( jobj_layout ) 

            # split layout into two part
            layout_start, layout_end = jlayout_str.split(jobj_layout_str)

            # updated layout
            update_layout_str = layout_start + sobj + layout_end
            return update_layout_str

    return ""


def get_max_indentation ( jlayout_str, indentation ) :

    jlayout_str = jlayout_str.lstrip()

    if jlayout_str[0] == '{' :
        jlayout_str = '[ ' + jlayout_str + ' ]' 

    jlayout = json.loads( jlayout_str )

    for obj in jlayout : 
        if obj["type"] == "rows" :
            for row in obj["rows"] :
                if row["type"] == "rows" :
                    indentation = get_max_indentation ( '[ ' + str(json.dumps(row)) + ' ]', indentation ) + 1
    return indentation

             
def build_id_trees_layout ( jlayout_str, previd ) :

    jlayout_str = jlayout_str.lstrip()

    if jlayout_str[0] == '{' :
        jlayout_str = '[ ' + jlayout_str + ' ]' 

    jlayout = json.loads( jlayout_str )

    built_string = ""

    for obj in jlayout : 
        if obj["type"] == "rows" :
            idcollection = previd + " " + obj["id"]
            for row in obj["rows"] :
                if row["type"] == "rows" :
                    built_string = built_string + build_id_trees_layout ( '[ ' + str(json.dumps(row)) + ' ]', idcollection ) + "\n"
                elif row["type"] == "row" :
                    built_string = built_string + idcollection + " " + row["id"] + "\n"
        if obj["type"] == "row" :
            idcollection = previd + " "
            built_string = built_string + idcollection + row["id"] + "\n"

    built_string = built_string.replace("\n\n","\n")
    built_string = built_string.rstrip("\n")
    built_string = built_string.lstrip(" ")
    built_string = built_string.replace("\n ","\n")

    return built_string


def is_archive_only( jlayout_str ):

    correct_popup = 1

    if jlayout_str == "" :
        return 0

    id_tree = build_id_trees_layout(jlayout_str, "") 
    id_tree_rows = id_tree.split("\n")
    for id_row in id_tree_rows :
        obj = get_from_layout( jlayout_str, id_row.split(" ")[-1] )
        if obj["fields"][2]["type"] != "archive" :
            correct_popup = 0

    return correct_popup

def to_all_read_only( jlayout_str ):

    id_tree = build_id_trees_layout(jlayout_str, "") 
    id_tree_rows = id_tree.split("\n")
    for id_row in id_tree_rows :
        obj = get_from_layout( jlayout_str, id_row.split(" ")[-1] )
        obj["permission"] = "read_only"
        jlayout_str = set_to_layout ( jlayout_str, obj )

    return jlayout_str

def to_all_read_only_except( jlayout_str, objid ):

    id_tree = build_id_trees_layout(jlayout_str, "") 
    id_tree_rows = id_tree.split("\n")
    for id_row in id_tree_rows :
        obj = get_from_layout( jlayout_str, id_row.split(" ")[-1] )
        if id_row.split(" ")[-1] != objid :
            obj["permission"] = "read_only"
        else :
            obj["permission"] = "read_write"
        jlayout_str = set_to_layout ( jlayout_str, obj )

    return jlayout_str

def only_read_write_row( jlayout_str, objid ):

    only_row = 0
    id_tree = build_id_trees_layout(jlayout_str, "") 
    id_tree_rows = id_tree.split("\n")
    for id_row in id_tree_rows :
        obj = get_from_layout( jlayout_str, id_row.split(" ")[-1] )
        if id_row.split(" ")[-1] != objid :
            if obj["permission"] != "read_only" :
                only_row = 1
        else :
            if obj["permission"] != "read_write" :
                only_row = 1

    return only_row


def convert_to_indexed_string(input_str):

    # Split input into lines and then into lists of strings
    lines = [line.split() for line in input_str.strip().split('\n') if line.strip()]

    # Dictionary to store the index of each prefix at each depth
    prefix_indices = {}

    # Result list for output
    result = []

    for line in lines:
        current_prefix = []
        indices = []

        for depth, token in enumerate(line):
            current_prefix.append(token)
            prefix_tuple = tuple(current_prefix)

            # If this prefix hasn't been seen before, assign the next index
            if prefix_tuple not in prefix_indices:
                # Parent prefix is everything up to the previous token
                parent_prefix = tuple(current_prefix[:-1]) if depth > 0 else ()
                # Count existing siblings (prefixes with same parent)
                sibling_count = sum(1 for k in prefix_indices if k[:-1] == parent_prefix and len(k) == depth + 1)
                prefix_indices[prefix_tuple] = sibling_count

            # Add the index for this level
            indices.append(str(prefix_indices[prefix_tuple]))

        result.append('.'.join(indices))

    # Format output to match the input style
    return '\n'.join(result) + '\n'

def add_row_number_to_layout ( layout_str ):

    id_tree = build_id_trees_layout ( layout_str, "" )
    prefixes = convert_to_indexed_string( id_tree ) 
    lines = id_tree.split("\n")
    prefixes_list = prefixes.split("\n")

    maxspaces = 10 
    nspaces_set = 0
    nspaces_offset = 0
    index = 0
    maxind = 0
    for line in lines :
        if ( len(prefixes_list[index]) > maxind ):
            maxind = len(prefixes_list[index])
        index = index + 1

    index = 0
    for line in lines :
        element_lst = line.split(" ")
        element_idx = 0
        for element in element_lst :

            obj = get_from_layout( layout_str, element )

            if obj["type"] == "row" :
                if starts_with_inital_tag_number ( obj["fields"][0]["display"] ) == 1 :
                    obj["fields"][0]["display"] = remove_initial_tag_number ( obj["fields"][0]["display"] )

                nspaces = (maxind-len(prefixes_list[index]))

                if ( obj["fields"][0]["display"][0:maxind] != (prefixes_list[index] + maxspaces * " " )[0:maxind] ) :
                    obj["fields"][0]["display"] = prefixes_list[index] + nspaces * " " + " " + obj["fields"][0]["display"]

            elif obj["type"] == "rows" :

                nspaces_set = 0
                nspaces_offset = 0
                if starts_with_inital_tag_number ( obj["display"] ) == 1 :
                    obj["display"] = remove_initial_tag_number ( obj["display"] )

                prefix = ""
                prefix_n = 0
                for num in prefixes_list[index].split(".") :
                    if prefix_n <= element_idx :
                        prefix = prefix + num + "."
                    prefix_n = prefix_n + 1
                prefix = prefix[:-1] 

                obj["display"] = prefix + " " + obj["display"]

            layout_str = set_to_layout ( layout_str, obj )
            element_idx = element_idx + 1

        index = index + 1

    return layout_str

def starts_with_inital_tag_number ( string ):
    pattern = r'^\d+(?:\.\d+)* '
    return bool(re.match(pattern,string))

def remove_initial_tag_number ( string ):
    string = string.split(' ', 1)[1] if ' ' in string else string
    string = string.lstrip()
    return string

def get_absolute_path_if_exists(path):

    absolute_path = os.path.abspath(os.path.expanduser(path))
    if os.path.exists(absolute_path):
        return absolute_path
    else:
        return path

def layout_to_absolute_paths( jlayout_str ):

    id_tree = build_id_trees_layout(jlayout_str, "") 
    id_tree_rows = id_tree.split("\n")
    for id_row in id_tree_rows :
        obj = get_from_layout( jlayout_str, id_row.split(" ")[-1] )
        if obj["type"] == "row" :
            if obj["fields"][2]["type"] == "path" :
                if ( obj["fields"][2]["display"] != "" ) :
                    obj["fields"][2]["display"] = get_absolute_path_if_exists( obj["fields"][2]["display"] )
                    jlayout_str = set_to_layout( jlayout_str, obj)
    return jlayout_str

